#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>

#define SIZE 512
#define ZERO '\0'
#define NUM 32
#define SEP " "
#define None_Redir 0
#define In_Redir 1
#define Out_Redir 2
#define Append_Redir 3
#define SkipPath(p) do{ p += (strlen(p) - 1); while(*p != '/') p--; }while(0)
#define SkipSpace(cmd, pos) do{ while(isspace(cmd[pos])) pos++;  }while(0)
int Redir = None_Redir;
char cwd[SIZE * 2];
char* filename = NULL;
int lastcode = 0;
char* gArgv[NUM];
void Die(){
    exit(-1);
}
const char* GetUser(){
    const char* user = getenv("USER");
    if(user == NULL)   return "unkown";
    else    return user;
}
const char* GetName(){
    const char* name = getenv("HOSTNAME");
    if(name == NULL)    return "unkown";
    else    return name;
}
const char* GetPwd(){
    const char* pwd = getenv("PWD");
    if(pwd == NULL)    return "unkown";
    else    return pwd;
}
void MakeCommandLineAndPrint(char Line[], size_t size){
    const char* user = GetUser();
    const char* name = GetName();
    const char* pwd = GetPwd();
    SkipPath(pwd); 
    snprintf(Line, size, "[%s@%s %s]> ", user, name, strlen(pwd) == 1 ? "/" : pwd + 1);
    printf("%s", Line);
    fflush(stdout);
}
int GetUserCommand(char command[], size_t size){
    char* s = fgets(command, size, stdin);
    if(s == NULL)
        return -1;
    command[strlen(command) - 1] = ZERO;
    return strlen(command);
}
void SpilitCommand(char command[], size_t size){
    (void)size;
    gArgv[0] = strtok(command, SEP);
    int index = 1;
    while((gArgv[index++] = strtok(NULL, SEP)));
}
void ExecuteCommand(){
    pid_t id = fork();
    if(id < 0)  Die();
    if(id == 0)
    {
        if(filename)
        {
            if(Redir == In_Redir)
            {
                int fd = open(filename, O_RDONLY);
                dup2(fd, 0);
            }
            else if(Redir == Out_Redir)
            {
                int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0666);
                dup2(fd, 1);
            }
            else if(Redir == Append_Redir)
            {
                int fd = open(filename, O_CREAT | O_WRONLY | O_APPEND, 0666);
                dup2(fd, 1);
            }
            else
                {}
        }
        execvp(gArgv[0], gArgv);
        exit(errno);
    }
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if(rid > 0)
    {
        lastcode = WEXITSTATUS(status);
        if(lastcode != 0)
            printf("%s: %s %d\n", gArgv[0], strerror(lastcode), lastcode);
    }
}
const char* GetHome(){
    return getenv("HOME");
}
void Cd(){
    const char* path = gArgv[1];
    if(path == NULL)
        path = GetHome();
    chdir(path);
    char temp[SIZE * 2];
    getcwd(temp, sizeof(temp));
    snprintf(cwd, sizeof(cwd), "PWD=%s", temp);
    putenv(cwd);
}
void CheckRedir(char cmd[]){
    int pos = 0;
    int end = strlen(cmd);
    while(pos < end)
    {
        if(cmd[pos] == '>')
        {
            if(cmd[pos + 1] == '>')
            {
                cmd[pos++] = 0;
                pos++;
                Redir = Append_Redir;
                SkipSpace(cmd, pos);
                filename = cmd + pos;
            }
            cmd[pos++] = 0;
            Redir = Out_Redir;
            SkipSpace(cmd, pos);
            filename = cmd + pos;
        }
        else if(cmd[pos] == '<')
        {
            cmd[pos++] = 0;
            Redir = In_Redir;
            SkipSpace(cmd, pos);
            filename = cmd + pos;
        }
        else
            pos++;
    }
}
int CheckBuildin(){
    const char* command = gArgv[0];
    if(strcmp(command, "cd") == 0)
    {
        Cd();
        return 1;
    }
    else if(strcmp(command, "echo") == 0 && strcmp(gArgv[1], "$?") == 0)
    {
        printf("%d\n", lastcode);
        lastcode = 0;
        return 1;
    }
    return 0;
}
int main()
{
    while(1) {
        Redir = None_Redir;
        filename = NULL;
        //1.自己输入一个命令行
        char command[512];
        MakeCommandLineAndPrint(command, sizeof(command));
        //2。获取用户输入的命令行
        char usercommand[SIZE];
        int i = GetUserCommand(usercommand, sizeof(usercommand));
        if(i <= 0)  return 1;
        CheckRedir(usercommand);
        //3.命令行字符串分割
        SpilitCommand(usercommand, sizeof(usercommand));
        //4.判断内建命令
        i = CheckBuildin();
        if(i) continue;
        //n.执行命令
        ExecuteCommand();
    }
    return 0;
}
